Dash Anomalías de temperatura#
La apliclación desplegada tiene como objetivo de visualización los cambios de temperatura cercana a la superficie en todo el mundo, entre 1850 a 2018. El archivo presenta anomalías mensuales promedio de temperatura cercana a la superficie, en relación con el período 1961–1990.
En general, este conjunto de datos muestra un aumento del calentamiento promedio global desde mediados del siglo 19 y en los últimos años, en consonancia con otros análisis. Este fenómeno se debe a múltiples factores, incluidos una mejor representación del calentamiento del Ártico y una mejor comprensión de los sesgos en evolución en las mediciones sobre la superficie del mar y la tierra. Para revisar a detalle el proceso de obtención y procesamiento de los datos, visite la información de referencia al final de este documento.
# Importanción de librerías
import pandas as pd
from time import mktime
from datetime import datetime, timedelta
import time
import seaborn as sns
import matplotlib.pyplot as plt
import pickle
import warnings
warnings.filterwarnings("ignore")
Manejo de la base de datos#
Previo a la construcción de la aplicación, es necesario procesar la base de datos. En este caso, utilizamos la librería pandas para leer los datos y configurar la estructura para que esté acorde con el formato de series de tiempo.
# Leer los datos
temperature_URL = "https://raw.githubusercontent.com/SandraMaldonado19/Dash_PF_Dataviz/main/originaldata_temp.csv"
df_temperature = pd.read_csv(temperature_URL)
df_temperature.head()
| year | january | february | march | april | may | june | july | august | september | october | november | december | yearmean | Unnamed: 14 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1850 | -0.675 | -0.333 | -0.591 | -0.589 | -0.508 | -0.344 | -0.160 | -0.208 | -0.385 | -0.533 | -0.283 | -0.404 | -0.418 | NaN |
| 1 | 1850 | 53.000 | 47.000 | 44.000 | 48.000 | 49.000 | 49.000 | 52.000 | 55.000 | 57.000 | 55.000 | 58.000 | 58.000 | NaN | NaN |
| 2 | 1851 | -0.201 | -0.469 | -0.646 | -0.542 | -0.198 | -0.137 | -0.097 | -0.102 | -0.091 | -0.008 | -0.082 | -0.228 | -0.233 | NaN |
| 3 | 1851 | 58.000 | 55.000 | 56.000 | 55.000 | 54.000 | 53.000 | 54.000 | 59.000 | 47.000 | 51.000 | 48.000 | 48.000 | NaN | NaN |
| 4 | 1852 | -0.375 | -0.477 | -0.560 | -0.585 | -0.127 | -0.084 | 0.005 | -0.136 | -0.002 | -0.172 | -0.305 | 0.065 | -0.229 | NaN |
# Eliminar columna vacía
df_temperature = df_temperature.drop(df_temperature.columns[df_temperature.columns.str.contains('Unnamed')], axis=1)
df_temperature.head()
| year | january | february | march | april | may | june | july | august | september | october | november | december | yearmean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1850 | -0.675 | -0.333 | -0.591 | -0.589 | -0.508 | -0.344 | -0.160 | -0.208 | -0.385 | -0.533 | -0.283 | -0.404 | -0.418 |
| 1 | 1850 | 53.000 | 47.000 | 44.000 | 48.000 | 49.000 | 49.000 | 52.000 | 55.000 | 57.000 | 55.000 | 58.000 | 58.000 | NaN |
| 2 | 1851 | -0.201 | -0.469 | -0.646 | -0.542 | -0.198 | -0.137 | -0.097 | -0.102 | -0.091 | -0.008 | -0.082 | -0.228 | -0.233 |
| 3 | 1851 | 58.000 | 55.000 | 56.000 | 55.000 | 54.000 | 53.000 | 54.000 | 59.000 | 47.000 | 51.000 | 48.000 | 48.000 | NaN |
| 4 | 1852 | -0.375 | -0.477 | -0.560 | -0.585 | -0.127 | -0.084 | 0.005 | -0.136 | -0.002 | -0.172 | -0.305 | 0.065 | -0.229 |
# Eliminar filas intermedias
df_temperature = df_temperature.drop(df_temperature.index[1::2]).reset_index(drop=True)
df_temperature.head()
| year | january | february | march | april | may | june | july | august | september | october | november | december | yearmean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1850 | -0.675 | -0.333 | -0.591 | -0.589 | -0.508 | -0.344 | -0.160 | -0.208 | -0.385 | -0.533 | -0.283 | -0.404 | -0.418 |
| 1 | 1851 | -0.201 | -0.469 | -0.646 | -0.542 | -0.198 | -0.137 | -0.097 | -0.102 | -0.091 | -0.008 | -0.082 | -0.228 | -0.233 |
| 2 | 1852 | -0.375 | -0.477 | -0.560 | -0.585 | -0.127 | -0.084 | 0.005 | -0.136 | -0.002 | -0.172 | -0.305 | 0.065 | -0.229 |
| 3 | 1853 | -0.233 | -0.404 | -0.280 | -0.386 | -0.268 | -0.142 | -0.083 | -0.057 | -0.250 | -0.392 | -0.411 | -0.337 | -0.270 |
| 4 | 1854 | -0.381 | -0.361 | -0.243 | -0.334 | -0.291 | -0.299 | -0.179 | -0.239 | -0.217 | -0.095 | -0.410 | -0.450 | -0.292 |
Aquí colocamos los meses que se encuentra en las filas como variables independientes en una sola columna, con su respectivo valor de anomalía, usando la función melt.
# Convertir a formato de serie de tiempo
df_timeseries = pd.melt(df_temperature, id_vars=['year'])
# Eliminar filas donde la columna 'variable' sea igual a 'yearmean'
df_timeseries = df_timeseries[df_timeseries['variable'] != 'yearmean']
# Cambiar el nombre de la columna 'variable' a 'month'
df_timeseries = df_timeseries.rename(columns={'variable':'month'})
df_timeseries.head()
| year | month | value | |
|---|---|---|---|
| 0 | 1850 | january | -0.675 |
| 1 | 1851 | january | -0.201 |
| 2 | 1852 | january | -0.375 |
| 3 | 1853 | january | -0.233 |
| 4 | 1854 | january | -0.381 |
# Crear la nueva columna de fecha en formato %Y-%m
df_timeseries['date'] = pd.to_datetime(df_timeseries['year'].astype(str) + '-' + df_timeseries['month'], format='%Y-%B')
# Formatear la fecha para que solo muestre mes y año
df_timeseries['date'] = df_timeseries['date'].dt.strftime('%Y-%m')
df_timeseries
| year | month | value | date | |
|---|---|---|---|---|
| 0 | 1850 | january | -0.675 | 1850-01 |
| 1 | 1851 | january | -0.201 | 1851-01 |
| 2 | 1852 | january | -0.375 | 1852-01 |
| 3 | 1853 | january | -0.233 | 1853-01 |
| 4 | 1854 | january | -0.381 | 1854-01 |
| ... | ... | ... | ... | ... |
| 2083 | 2019 | december | 1.037 | 2019-12 |
| 2084 | 2020 | december | 0.693 | 2020-12 |
| 2085 | 2021 | december | 0.751 | 2021-12 |
| 2086 | 2022 | december | 0.768 | 2022-12 |
| 2087 | 2023 | december | -9.999 | 2023-12 |
2088 rows × 4 columns
df_timeseries = df_timeseries.sort_values(by=['date'])
# Eliminar las últimas tres filas
df_timeseries = df_timeseries.iloc[:-3]
df_timeseries
| year | month | value | date | |
|---|---|---|---|---|
| 0 | 1850 | january | -0.675 | 1850-01 |
| 174 | 1850 | february | -0.333 | 1850-02 |
| 348 | 1850 | march | -0.591 | 1850-03 |
| 522 | 1850 | april | -0.589 | 1850-04 |
| 696 | 1850 | may | -0.508 | 1850-05 |
| ... | ... | ... | ... | ... |
| 869 | 2023 | may | 0.871 | 2023-05 |
| 1043 | 2023 | june | 1.052 | 2023-06 |
| 1217 | 2023 | july | 1.150 | 2023-07 |
| 1391 | 2023 | august | 1.199 | 2023-08 |
| 1565 | 2023 | september | 1.347 | 2023-09 |
2085 rows × 4 columns
df_timeseries.to_csv("df_timeseries.csv", index=False)
Análisis Exploratorio de Datos - EDA#
Aquí se presenta un análisis de los datos. Incluye gráficos de la serie de tiempo en diversas escalas temporales (mensual, anual, decenal), gráficos de línea, gráficos de caja y bigotes, mapas de calor, proporcionando información sobre patrones y tendencias de la variable objetivo (Anomalía de temperatura (°C)) de forma interactiva. Asimismo, se explora la estacionariedad de la serie, información base para la construcción de modelos predictivos.
timeseries_URL = "https://raw.githubusercontent.com/SandraMaldonado19/Dash_PF_Dataviz/main/df_timeseries.csv"
df_tseries = pd.read_csv(timeseries_URL)
Media móvil de 12 meses#
El código inicia importando las bibliotecas necesarias y cargando un conjunto de datos desde un repositorio en github. Luego, define una función para calcular la media móvil y el intervalo de confianza de los datos. La aplicación Dash se crea y se diseña con un título y el gráfico de la serie de tiempo. Una función callback se define para actualizar el gráfico cuando el usuario coloca el cursor sobre él con la función ‘hover’, mostrando la media móvil, el intervalo de confianza y la media del año seleccionado. Finalmente, la aplicación se ejecuta.
import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio
from plotly.offline import iplot
# Definimos la función encargada de calcular la media móvil y el intervalo de
## En este código '1.96' es el valor z para un intervalo de confianza del 95%.
# Función para calcular la media móvil y el intervalo de confianza
def calculate_rolling_mean(df, window_size=12, num_of_std=1.96):
df['rolling_mean'] = df['value'].rolling(window=window_size).mean()
df['rolling_std'] = df['value'].rolling(window=window_size).std()
df['upper_band'] = df['rolling_mean'] + (df['rolling_std'] * num_of_std)
df['lower_band'] = df['rolling_mean'] - (df['rolling_std'] * num_of_std)
return df
df_timeseries = calculate_rolling_mean(df_tseries.copy())
# Crear la figura
fig = go.Figure()
# Agregar la serie temporal original
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['value'], mode='lines', name='Serie original', line=dict(color='blue')))
# Agregar la media móvil
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['rolling_mean'], mode='lines', name='12-Month Rolling Mean', line=dict(color='red')))
# Agregar el intervalo de confianza
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['upper_band'], mode='lines', name='Banda superior (95%)', line=dict(dash='dash', color='green')))
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['lower_band'], mode='lines', name='Banda inferior (95%)', line=dict(dash='dash', color='green')))
# Configurar el layout
fig.update_layout(title='Media Móvil de 12 Meses con Intervalo de Confianza', xaxis_title='Fecha', yaxis_title='Anomalía de Temperatura (°C)')
# Mostrar la figura
iplot(fig)
Se puede observar una tendencia de aumento en la serie luego de la etapa pre-industrial, lo que sugiere que la serie tiene un comportamiento no estacionario, con medias variables y en aumento para intervalos de 12 meses.
Media mensual multianual y distribución de datos: Boxplot#
Se graficó un boxplot Multianual por mes, que se utiliza para visualizar la distribución de los datos de Anomalías de Temperatura a lo largo de los meses del año. Cada caja en el gráfico representa un mes específico y muestra la variabilidad de los datos para ese mes a lo largo de varios años. El gráfico está diseñado de tal manera que cada caja (o ‘boxplot’) muestra la mediana (la línea en el medio de la caja), los cuartiles superior e inferior (los bordes superior e inferior de la caja), y los valores atípicos (los puntos por encima y por debajo de las líneas que se extienden desde la caja). Esto proporciona una visión clara de la distribución de los datos, permitiendo identificar tendencias y anomalías.
import plotly.graph_objects as go
import pandas as pd
# Convert 'month' to categorical type with correct order
ordered_months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
df_tseries['month'] = pd.Categorical(df_tseries['month'], categories=ordered_months, ordered=True)
# Create boxplots for each month
fig = go.Figure([go.Box(x=data['month'], y=data['value'], name=month, width=0.5) for month, data in df_tseries.groupby('month')])
# Add trend line (multi-year average)
trend_line = df_tseries.groupby('month')['value'].mean().reset_index()
fig.add_trace(go.Scatter(x=trend_line['month'], y=trend_line['value'],
mode='lines', line=dict(color='black', dash='dash'),
name='Media Multianual'))
# Adjust the layout of the figure
fig.update_layout(xaxis={'title': 'Mes'}, yaxis={'title': 'Anomalía de Temperatura'},
boxmode='group') # To group the boxplots on the x-axis
# Display the figure
fig.show()
El análisis de Anomalías de temperatura por meses indica que, por lo menos el 50% de los datos para todos los meses, se mantiene por debajo de las temperaturas preindustriales, con magnitudes que varían entre -0.22°C y -0.09°C, siendo el mes de octubre el que presenta temperaturas más similares a este período de referencia. Se conoce que, por lo menos el 25% superior de los datos presenta aumentos de temperatura para todos los meses.
Anomalías mensual-anual: mapa de calor#
# Meses ordenados
month_order = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
heatmap_data = df_tseries.pivot(index='month', columns='year', values='value')
heatmap_data = heatmap_data.reindex(month_order)
fig = go.Figure()
fig.add_trace(go.Heatmap(x=heatmap_data.columns, y=heatmap_data.index, z=heatmap_data.values,
colorscale='RdBu', colorbar=dict(title='Anomalía de Temperatura'), reversescale=True))
fig.update_layout(xaxis={'title': 'Año'}, yaxis={'title': 'Mes', 'categoryorder': 'array', 'categoryarray': month_order},
title='Mapa de Calor - Anomalías de Temperatura (°C)')
# Display the figure
fig.show()
A partir de este mapa de calor se han identificado tendencias significativas en los datos de temperatura. Tras la Revolución Industrial, que tuvo lugar a finales del siglo XIX, se ha registrado un incremento progresivo en las temperaturas hasta el cierre del siglo XX. Con el inicio del nuevo milenio, se ha observado una aceleración notable en este incremento de temperatura. Estos patrones globales coinciden con fenómenos de gran escala como la proliferación de la industrialización y el auge y desarrollo de las áreas urbanas, que son los principales contribuyentes a la emisión de gases de efecto invernadero (GEI) a gran escala. Estos gases son los causantes primordiales del calentamiento global debido a su producción intensiva.
Promedio decenal#
A continuación, se crea un gráfico de barras. Se define una función de devolución de llamada que se activa cuando el usuario interactúa con el gráfico de barras. Esta función calcula los promedios decenales y actualiza el gráfico de barras.
import pandas as pd
import plotly.express as px
import numpy as np
# Convertir la columna 'date' a tipo datetime si aún no lo está
df_tseries['date'] = pd.to_datetime(df_tseries['date'])
# Agregar una columna 'decade' para agrupar por década
df_tseries['decade'] = (df_tseries['year'] // 10) * 10
# Agregar una columna 'color' basada en la condición de la anomalía
df_tseries['color'] = df_tseries['value'].apply(lambda x: 'red' if x > 0 else 'blue')
# Calcular los promedios decenales
decade_means = df_tseries.groupby('decade').agg({'value': 'mean', 'color': 'first'}).reset_index()
# Crear el gráfico de barras con Plotly Express
fig = px.bar(decade_means, x='decade', y='value', color='color',
labels={'value': 'Anomalía de Temperatura'},
color_discrete_map={'red': 'red', 'blue': 'blue'},
title='Promedios Decenales de Anomalías de Temperatura')
# Modificar las etiquetas de los ticks en el eje x
fig.update_layout(
xaxis=dict(
tickmode='array',
tickvals=decade_means['decade'],
ticktext=[f"{decade}s" for decade in decade_means['decade']]
),
showlegend=False
)
La gráfica es consistente con el mapa de calor anterior, mostrando durante el siglo XX, especialmente entre 1940 y 1980, se empiezan a ver estas tendencias de aumento de la temperatura.
Visualización de máximos anuales#
El siguiente gráfico agrega líneas con la tendencia mensual de la temperatura de un año específico que seleccione el ususario. El usuario puede comparar cualquier año de preferencia con los años que poseen mayores aumentos; en esta caso, corresponden a 2016, 2019 y 2020.
import plotly.graph_objs as go
import pandas as pd
import numpy as np
from datetime import datetime
# Asegúrate de que los datos estén en el formato correcto
df_tseries['date'] = pd.to_datetime(df_tseries['date'])
df_tseries['year'] = df_tseries['date'].dt.year
df_tseries['month'] = df_tseries['date'].dt.month
# Encuentra los tres años más cálidos, excluyendo el año actual
current_year = datetime.now().year
top_years = df_tseries[df_tseries['year'] != current_year].groupby('year')['value'].mean().nlargest(3).index
# Encuentra los valores máximos para cada mes
max_values = df_tseries.groupby('month')['value'].max()
traces = []
markers = ['circle', 'square', 'diamond', 'cross', 'x']
# Añade las líneas para los años más cálidos
for i, year in enumerate(top_years):
df_year = df_tseries[df_tseries['year'] == year]
traces.append(go.Scatter(
x=df_year['month'],
y=df_year['value'],
mode='lines+markers',
name=str(year),
marker=dict(
symbol=markers[i % len(markers)],
size=10
)
))
# Añade las líneas para los años seleccionados
selected_years = [df_tseries['year'].max()]
for i, selected_year in enumerate(selected_years):
df_selected = df_tseries[df_tseries['year'] == selected_year]
traces.append(go.Scatter(
x=df_selected['month'],
y=df_selected['value'],
mode='lines+markers',
name=str(selected_year),
line={'color': 'black', 'width': 2},
marker=dict(
symbol=markers[(i + len(top_years)) % len(markers)],
size=10
)
))
# Añade la línea para los valores máximos
traces.append(go.Scatter(
x=np.arange(1, 13),
y=max_values,
mode='lines',
name='Máximo histórico',
line={'color': 'darkgrey', 'width': 2.5}
))
fig = go.Figure(data=traces)
fig.update_layout(
xaxis={'title': 'Mes', 'tickvals': list(range(1, 13)), 'ticktext': ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']},
yaxis={'title': 'Anomalía de temperatura (°C)'},
hovermode='closest'
)
fig.show()
Verificación de la estacionariedad de la serie#
Se utiliza la prueba estadística Dickey-Fuller y los plots ACF y PACF para verificar si la serie es estacionaria. Esto es indispensable para construir modelos de series temporales, como los modelos ARIMA, ya que la estacionariedad es un supuesto fundamental. Si las propiedades estadísticas de la serie no cambian a lo largo del tiempo, se pueden obtener predicciones más precisas.
import numpy as np, pandas as pd
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
result = adfuller(df_tseries.value)
adf_statistic = result[0]
p_value = result[1]
print("ADF:", adf_statistic)
print("p-value:", p_value)
ADF: -0.09427041182601259
p-value: 0.9500062407464049
Las tendencias en la serie muestran que la series no es estacionaria. Por lo tanto, debe aplicarse diferenciación para convertir la serie en estacionaria.
#----- Autocorrelación--------------------------------------------------------------------------------------------------------
import numpy as np, pandas as pd
from statsmodels.graphics.tsaplots import plot_acf
import matplotlib.pyplot as plt
from plotly.subplots import make_subplots
from statsmodels.tsa.stattools import acf
# Crear una figura con subplots
fig = make_subplots(rows=3, cols=2)
# Agregar los gráficos a los subplots correspondientes
fig.add_trace(go.Scatter(y=df_tseries.value, mode='lines', line=dict(color='blue'), showlegend=False, hovertemplate='Value: %{y}'), row=1, col=1)
fig.add_trace(go.Scatter(y=df_tseries.value.diff(), mode='lines', line=dict(color='red'), showlegend=False, hovertemplate='Diff: %{y}'), row=2, col=1)
fig.add_trace(go.Scatter(y=df_tseries.value.diff().diff(), mode='lines', line=dict(color='green'), showlegend=False, hovertemplate='Diff Diff: %{y}'), row=3, col=1)
# Para agregar los gráficos de autocorrelación, se necesita calcular los valores de autocorrelación
acf_values = acf(df_tseries.value, nlags=2080)
fig.add_trace(go.Scatter(y=acf_values, mode='lines', fill='tozeroy', line=dict(color='blue'), showlegend=False, hovertemplate='ACF: %{y}'), row=1, col=2)
diff1_values = df_tseries.value.diff()
acf_diff1_values = acf(diff1_values.dropna(), nlags=2080)
fig.add_trace(go.Scatter(y=acf_diff1_values, mode='lines', fill='tozeroy', line=dict(color='red'), showlegend=False, hovertemplate='ACF Diff: %{y}'), row=2, col=2)
diff2_values = df_tseries.value.diff().diff()
acf_diff2_values = acf(diff2_values.dropna(), nlags=2080)
fig.add_trace(go.Scatter(y=acf_diff2_values, mode='lines', fill='tozeroy', line=dict(color='green'), showlegend=False, hovertemplate='ACF Diff Diff: %{y}'), row=3, col=2)
# Agregar títulos a las gráficas
fig.add_annotation(dict(text='Serie original', x=0.20, y=1.1, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='1er Orden de diferenciación', x=0.15, y=0.66, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='2do Orden de diferenciación', x=0.15, y=0.25, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='ACF de la Serie original', x=0.85, y=1.1, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='ACF del 1er Orden de diferenciación', x=0.88, y=0.66, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='ACF del 2do Orden de diferenciación', x=0.88, y=0.25, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
El gráfico indica que con un orden de diferenciación la serie se volvió en más estacionaria. Para comprobar la estacionariedad, aplicamos la prueba ADF nuevamente.
from statsmodels.tsa.stattools import adfuller
# Aplica la función diff() una vez y elimina los valores NaN
diff_values = df_tseries.value.diff().dropna()
result = adfuller(diff_values)
adf_statistic = result[0]
p_value = result[1]
print("ADF:", adf_statistic)
print("p-value:", p_value)
ADF: -15.697771241158453
p-value: 1.408231089800588e-28
Dado que p-value < 0.05, se comprueba que la serie es estacionaria aplicando un orden de diferenciación.